package com.nx.platform.openentry.annotation.parser;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import com.nx.arch.nxrpc.client.support.SCFClientMethodSignatureKeyGenerator;
import com.nx.arch.nxrpc.server.contract.annotation.ServiceContract;
import com.nx.platform.openentry.annotation.EntryMethod;
import com.nx.platform.openentry.entity.MethodEntity;
import com.nx.platform.openentry.entity.ParamEntity;
import com.nx.platform.openentry.entity.response.OpenEntryCmdDto;
import com.nx.platform.openentry.entity.response.OpenEntryModuleDTO;
import com.nx.platform.openentry.system.RpcUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * @author nx
 * @version 创建时间：2017-6-13 上午10:55:24
 */
@Slf4j
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class AnnotationScan implements ApplicationContextAware {
    
    private static Map<String, MethodEntity> mapCommonEntry = new HashMap<>();
    
    private static Long moduleId = -1L;
    
    private static Set<String> cmdIdSet = new HashSet<String>();
    
    private static Map<Class<?>, Object> beanMap = null;
    
    public void start() {
        initAnnotation();
    }
    
    private ApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
        throws BeansException {
        this.applicationContext = applicationContext;
    }
    
    @PostConstruct
    public void init() {
        initAnnotation(applicationContext);
    }
    
    /**
     * 从scf容器获取对象实例
     */
    public static void initAnnotation() {
        if (CollectionUtils.isEmpty(mapCommonEntry)) {
            /** 扫描注解用于反射调用 */
            scanMethodInvoke(null);
        }
    }
    
    /**
     * 从spring容器获取对象实例
     *
     * @param context
     */
    public static void initAnnotation(final ApplicationContext context) {
        
        log.info("openentry init by spring context start");
        if (CollectionUtils.isEmpty(mapCommonEntry)) {
            /** 扫描注解用于反射调用 */
            scanMethodInvoke(context);
        }
    }
    
    /**
     * 扫描注解用于反射调用
     */
    private static void scanMethodInvoke(final ApplicationContext applicationContext) {
        log.info("{OpenEntryInit}{initMethod} scan OpenEntryMethodCommon start........");
        
        final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        // 扫描注解用于反射调用--matf
        PacakgeScanUtil.scanScfPackage(implClass -> {
            Class<?> interfacesClass = null;
            for (Class<?> cla : implClass.getInterfaces()) {
                ServiceContract serviceContract = cla.getAnnotation(ServiceContract.class);
                if (serviceContract != null) {
                    interfacesClass = cla;
                }
            }
            if (interfacesClass == null) {
                // 只处理scf接口转发
                return;
            }
            // 获取本服务名
            String module = System.getProperty("scf.service.name");
            String logStr = "desc=scan_open_entry logid=" + System.currentTimeMillis();
            OpenEntryModuleDTO moduleDTO = RpcUtil.openEntryService.getModuleConfByModuleName(logStr, module);
            if (moduleDTO == null) {
                log.error("{} err=module_not_register module={}", logStr, module);
                return;
            }
            
            moduleId = moduleDTO.getId();
            Object bean = null;
            if (applicationContext == null) {
                bean = beanMap.get(interfacesClass);
            } else {
                bean = applicationContext.getBean(interfacesClass);
            }
            if (bean == null) {
                log.error("{OpenEntryInit}{initMethod} getSpringBean error,interfacesClass:{}", interfacesClass);
                return;
            }
            // 检查scf接口实现类，所有openentry method
            for (Method interMethod : interfacesClass.getMethods()) {
                try {
                    try {
                        Method method = Object.class.getMethod(interMethod.getName(), interMethod.getParameterTypes());
                        if (method != null) {
                            log.warn("{OpenEntryInit}{initMethod}this is Object Method,MethodName:{}", interMethod.getName());
                            continue;
                        }
                    } catch (Exception e) {
                        // 不重要的错误
                    }
                    Method implMethod = implClass.getMethod(interMethod.getName(), interMethod.getParameterTypes());
                    EntryMethod method = implMethod.getAnnotation(EntryMethod.class);
                    String cmdName = "";
                    boolean publicAccess = true;
                    String redirectModulePath = "";
                    if (method != null) { // 优先使用实现类注解
                        cmdName = method.value();
                        publicAccess = method.needLogin();
                        redirectModulePath = method.redirectModulePath();
                    } else {
                        // 不加注解的直接放弃
                        continue;
                    }
                    
                    if (StringUtils.isBlank(cmdName)) { // 实现类上没有注解 使用接口注解
                        method = interMethod.getAnnotation(EntryMethod.class);
                        if (method != null) {
                            cmdName = method.value();
                            publicAccess = method.needLogin();
                            redirectModulePath = method.redirectModulePath();
                        }
                    }
                    
                    if (StringUtils.isBlank(cmdName)) {
                        log.info("{OpenEntryInit}{initMethod}Method.value is NULL className:{},methodName:{}", implClass, implMethod.getName());
                        continue;
                    }
                    Method interfacesMethod = interfacesClass.getMethod(implMethod.getName(), implMethod.getParameterTypes());
                    if (interfacesMethod == null) {
                        log.error("{OpenEntryInit}{initMethod}获取该实现类的interfacesClass失败请检查:implClass{},interfacesClass:{}", implClass, interfacesClass);
                        continue;
                    }
                    if (cmdIdSet.contains(cmdName)) {
                        log.error("{OpenEntryInit}{initMethod} already exists this method-------class:{},method:{}", implClass.getName(), cmdName);
                        throw new Exception("already exists this entryMethodName:" + cmdName + " in implClass:" + implClass.toString());
                    }
                    cmdIdSet.add(cmdName);
                    log.info("{OpenEntryInit}{initMethod} init method start-------class:{},method:{}", implClass.getName(), cmdName);
                    Long moduleIdTemp = moduleId;
                    int pubcliAccessTemp = publicAccess ? 1 : 0;
                    if (moduleIdTemp != -1) {
                        // 1.查询open_command_config表中是否存在moduleId cmdName
                        OpenEntryCmdDto cmdConf = RpcUtil.openEntryService.getCmdByModuleCmd(logStr, moduleIdTemp, cmdName);
                        String methodSign = new SCFClientMethodSignatureKeyGenerator().adapt(implClass.getSimpleName(), implMethod);
                        if (cmdConf != null) {// 更新
                            OpenEntryCmdDto cmdConfForUpdate = new OpenEntryCmdDto();
                            cmdConfForUpdate.setId(cmdConf.getId());
                            cmdConfForUpdate.setCommandName(cmdName);
                            cmdConfForUpdate.setIsPublicAccess(pubcliAccessTemp);
                            cmdConfForUpdate.setMethodSign(methodSign);
                            cmdConfForUpdate.setRedirectModulePath(redirectModulePath);
                            cmdConfForUpdate.setUpdateTime(System.currentTimeMillis());
                            RpcUtil.openEntryService.updateCommandConfigure(logStr, cmdConfForUpdate);
                        } else {// 插入
                            OpenEntryCmdDto cmdConfForAdd = new OpenEntryCmdDto();
                            cmdConfForAdd.setModuleId(moduleIdTemp);
                            cmdConfForAdd.setCommandName(cmdName);
                            cmdConfForAdd.setIsPublicAccess(pubcliAccessTemp);
                            cmdConfForAdd.setMethodSign(methodSign);
                            cmdConfForAdd.setRedirectModulePath(redirectModulePath);
                            cmdConfForAdd.setCreateTime(System.currentTimeMillis());
                            cmdConfForAdd.setUpdateTime(System.currentTimeMillis());
                            RpcUtil.openEntryService.insertCommandConfigure(logStr, cmdConfForAdd);
                        }
                    }
                    // 接口方法参数名
                    String[] implParameterNames = parameterNameDiscoverer.getParameterNames(implMethod);
                    List<ParamEntity> openParamEntries = new ArrayList<>();
                    MethodEntity openCommonEntry = new MethodEntity();
                    openCommonEntry.setObj(bean);
                    openCommonEntry.setMethod(interfacesMethod);
                    openCommonEntry.setParams(openParamEntries);
                    // 接口注解参数名
                    Class<?>[] parameterTypes = implMethod.getParameterTypes();
                    for (int i = 0; i < parameterTypes.length; i++) {
                        Class<?> parameterClass = parameterTypes[i];
                        ParamEntity openParamEntry = new ParamEntity();
                        openParamEntry.setParamClass(parameterClass);
                        openParamEntry.setParamName(implParameterNames[i]);
                        openParamEntries.add(openParamEntry);
                    }
                    mapCommonEntry.put(cmdName, openCommonEntry);
                    // log.info("{OpenEntryInit}{initMethod} scan class:" + interfacesClass.getName() + "." + cmdName + ":{}",
                    // openCommonEntry);
                    log.info("{OpenEntryInit}{initMethod}toMap cmd:{} openCommonEntry:{}", cmdName, openCommonEntry);
                } catch (Exception e) {
                    log.error("{OpenEntryInit}{initMethod} OpenEntryMethodCommon error", e);
                }
            }
        });
    }
    
    public static Map<String, MethodEntity> getMapCommonEntry() {
        return mapCommonEntry;
    }
    
    public static void setMapCommonEntry(Map<String, MethodEntity> mapCommonEntry) {
        AnnotationScan.mapCommonEntry = mapCommonEntry;
    }
    
}
